#include "Player.h"

using namespace std;

extern Environment env;
extern BlockCraft* app;

Player::Player(void) :
        lastUpdate(-1.0f),
        keyAPressed(false),
        keyDPressed(false),
        xSpeed(0.14f),
        //targetingBlock(false),
        jumpSpeed(0.4f),
        gravity(.03f),
        sneaking(false),
        reach(4.0f),
        speedModifier(1.0f) {
    pos.set(env.sX/2.0f, env.sY/2);
    vel.set(0.0f, -1.0f);
    currentItem = new Item(0);
    inventory.addItem(DIAMONDPICKAXE);
    inventory.addItem(DIAMONDSHOVEL);
    synchronizeInventory();
}

Player::~Player(void) {
    delete currentItem;
}

void Player::move(char direction) {
    float mag = xSpeed;
    vel.x = ofClamp(
        vel.x + (direction == 'r' ? mag : -mag),
        -mag, mag);
}

void Player::jump() {
    if (vel.y) return;

    vel.y = jumpSpeed;
}

void Player::update() {
    if (lastUpdate == -1.0f) {
        lastUpdate = ofGetElapsedTimef();
        return;
    }

    // adjust speed
    speedModifier = (sneaking ? 0.75f : 1.0f);

    // Update aim, and stop hitting previously targeted block.
    aim();

    /* if (targetingBlock) { //taken care of in aim()
        target.x = (int)aimAt.x;
        target.y = (int)aimAt.y;
    } else {
        target.set(-1,-1);
    } */

	// taken care of in Environment.cpp
    /* if (((target != target_prev) || (!targetingBlock)) &&
        (target_prev.x != -1.0f))
        env.wgrid()[(int)target_prev.y][(int)target_prev.x]->stopHitting(); */

    int collisionPoint = 0;

    // Work out movement decay and change
    float elapsedTime =
        ofGetElapsedTimef() - lastUpdate;

    vel.y -= gravity;
    if (!hasMovementKeyDown()) vel.x *= 0.6f;

    // Collision detection and motion
    moveWithCollisionDetection();
    if (hasMovementKeyDown()) resumeVelocity();

    char buf[80];

    sprintf(buf,
        "Position: %8f, %8f",
        pos.x, pos.y);
    debugPos = string(buf);

    sprintf(buf,
        "Velocity: %8f, %8f",
        vel.x, vel.y);
    debugVel = string(buf);

    lastUpdate = ofGetElapsedTimef();
}

void Player::aim() {
    float x = app->mousePos.x,
          y = app->mousePos.y;

    float dYdX = (y-(pos.y+1.5)) / (x-pos.x);

	int xSide = (int) (x > pos.x);
	int ySide = (int) (y > pos.y + 1.5);

    float xStart = (pos.x) + xSide;
    float yStart = (pos.y+1.5) + ySide;

    int xDirection = x >= pos.x ? 1 : -1;
    int yDirection = y >= pos.y + 1.5 ? 1 : -1;

	target.block_old = target.block;

	target.block.x = -1;
	target.block.y = -1;
	target.block.intercept.x = -1;
	target.block.intercept.y = -1;
	target.block.distance =  reach;
	target.block.direction.x = xDirection;
	target.block.direction.y = yDirection;

	target.active = false;

	target.point.set(x, y);

	xTargetPath.clear();
	yTargetPath.clear();

    for (int xCurrent = xStart + xSide; ; xCurrent += xDirection) { // check x
		int checkX = xCurrent - 1;
		int checkY = (int)(yStart + (xCurrent - xStart)*dYdX) - ySide;
		float hitY = (yStart + (xCurrent - xStart)*dYdX) - ySide;

		float distance = sqrt(pow(xCurrent-xStart,2)+pow((xCurrent-xStart)*dYdX,2));

		// check reach
		// check boundries
		if (distance > target.block.distance || !(0 <= checkY && checkY < env.sY) || !(0 <= checkX && checkX < env.sX)) {
			break;
		}

		block checkBlock;
		checkBlock.x = checkX;
		checkBlock.y = checkY;
		checkBlock.intercept.x = checkX - (xSide - 1);
		checkBlock.intercept.y = hitY;
		checkBlock.distance = distance;
		checkBlock.direction.x = xDirection;
		checkBlock.direction.y = 0;

		if(app->isInDebugMode()) { // no need to waste memory
			xTargetPath.push_back(checkBlock);
		}

		if (!env.blockByCoord(checkX,checkY).transparent) {
			target.block = checkBlock;
			target.active = true;
			target.point.set(checkX - (xSide - 1), hitY);
			break; // break if it's not transparent
		}
	}

    for (int yCurrent = yStart + ySide; ; yCurrent += yDirection) { // check y
		int checkY = yCurrent - 1;
		int checkX = (int)(xStart + (yCurrent - yStart)*(1/dYdX)) - xSide;
		float hitX = (xStart + (yCurrent - yStart)*(1/dYdX)) - xSide;

		float distance = sqrt(pow(yCurrent-yStart,2)+pow((yCurrent-yStart)*(1/dYdX),2));

		// check reach
		// check boundries
		if (distance > target.block.distance || !(0 <= checkY && checkY < env.sY) || !(0 <= checkX && checkX < env.sX)) {
			break;
		}

		block checkBlock;
		checkBlock.x = checkX;
		checkBlock.y = checkY;
		checkBlock.intercept.x = hitX;
		checkBlock.intercept.y = checkY - (ySide - 1);
		checkBlock.distance = distance;
		checkBlock.direction.x = 0;
		checkBlock.direction.y = yDirection;

		if(app->isInDebugMode()) { // no need to waste memory
			yTargetPath.push_back(checkBlock);
		}

		if (!env.blockByCoord(checkX,checkY).transparent) {
			target.block = checkBlock;
			target.active = true;
			target.point.set(hitX, checkY - (ySide - 1));
			break; // break if it's not transparent
		}
	}
}

void Player::draw() {
    /* OLD BALL STYLE
    ofCircle(pos.x, pos.y+0.5f, 0.5f);
    ofCircle(pos.x, pos.y+1.5f, 0.5f);
    */

    // Draw target ray
    ofEnableAlphaBlending();
    ofEnableSmoothing();
    ofSetColor(0, 0, 0, 64);
    ofLine(pos.x, pos.y+1.5, app->mousePos.x, app->mousePos.y);
    ofDisableAlphaBlending();
    //ofLine(aimAt.x-0.25, aimAt.y, aimAt.x+0.25, aimAt.y);
    //ofLine(aimAt.x, aimAt.y-0.25, aimAt.x, aimAt.y+0.25);
    ofDisableSmoothing();

    // New, stick figure style
    ofSetColor(0x812718);
    ofEnableSmoothing();
    int d = target.point.x >= pos.x ? 1 : -1;
    ofFill();
    ofRect(pos.x-0.25f, pos.y+1.25f, 0.5f, 0.5f);
    ofSetColor(0x000000);
    ofNoFill();
    ofRect(pos.x-0.25f, pos.y+1.25f, 0.5f, 0.5f);
    // Body
    ofLine(pos.x, pos.y+1.25f, pos.x, pos.y+0.5f);
    // Arms
    ofLine(pos.x, pos.y+1.00f, pos.x+0.25f, pos.y+0.5f);
    ofLine(pos.x, pos.y+1.00f, pos.x-0.25f, pos.y+0.5f);
    // Legs
    ofLine(pos.x, pos.y+0.50f, pos.x-0.25f, pos.y);
    ofLine(pos.x, pos.y+0.50f, pos.x+0.25f, pos.y);
    // Face
    ofLine(pos.x+d*0.25f, pos.y+1.375f, pos.x+d*0.125f, pos.y+1.375f);
    ofLine(pos.x+d*0.125f, pos.y+1.375f, pos.x+d*0.125f, pos.y+1.5f);
    ofRect(pos.x+d*0.125f, pos.y+01.625, d*0.0625f, 0.0625f);
    ofDisableSmoothing();
    ofFill();

	if(app->isInDebugMode()) {
		for(int i = 0; i < xTargetPath.size(); i++) {
			// highlight blocks in the target path

			ofEnableAlphaBlending();
			ofSetColor(102,0,0,128);
			switch(xTargetPath[i].direction.x) {
				case 1:
					ofRect(xTargetPath[i].x, xTargetPath[i].y, .25, 1);
					break;
				case -1:
					ofRect(xTargetPath[i].x+.75, xTargetPath[i].y, .25, 1);
					break;
			}
			ofCircle(xTargetPath[i].intercept.x, xTargetPath[i].intercept.y, .25);
			ofDisableAlphaBlending();
		}
		for(int i = 0; i < yTargetPath.size(); i++) {
			// highlight blocks in the target path

			ofEnableAlphaBlending();
			ofSetColor(0,102,0,128);
			switch(yTargetPath[i].direction.y) {
				case 1:
					ofRect(yTargetPath[i].x, yTargetPath[i].y, 1, .25);
					break;
				case -1:
					ofRect(yTargetPath[i].x, yTargetPath[i].y+.75, 1, .25);
					break;
			}
			ofCircle(yTargetPath[i].intercept.x, yTargetPath[i].intercept.y, .25);
			ofDisableAlphaBlending();
		}
		ofEnableAlphaBlending();
		ofSetColor(0,0,102,128);
		ofCircle(target.point.x, target.point.y, .25);
		ofCircle(target.point.x, target.point.y, .25);
		ofDisableAlphaBlending();
	}
}

void Player::drawInventory() {
    inventory.draw();
}

Item* Player::getCurrentInventoryItem() {
    return inventory.getCurrentItem();
}

int Player::getCurrentInventoryItemID() {
    return inventory.getCurrentItemID();
}

bool Player::hasRoomInInventoryForItem(ItemParticle *itm) {
    return inventory.hasRoomForItem(itm->getID());
}

bool Player::pickUpItem(ItemParticle *itm) {
    return inventory.addItem(itm->getID());
    synchronizeInventory();
}

bool Player::deleteItemsFromInventory(int qty) {
    return inventory.removeItem();
}

void Player::throwItem() {
    int curID = inventory.getCurrentItem()->ID;
    if (curID == 0) return;

    inventory.removeItem();

	float throwAngle = atan2f(target.point.y-pos.y-1.5f, target.point.x-pos.x) + ofRandomf()/10.0f;

    env.itemParticles.push_back(new ItemParticle(
        ofPoint(pos.x, pos.y+1.0f),
        ofPoint(cosf(throwAngle)/4.0f, sinf(throwAngle)/4.0f),
        curID,
        120.0f, 0.7f));
}

void Player::scrollInventory(bool scrollingDown) {
    inventory.scroll(scrollingDown);
    synchronizeInventory();
}

void Player::scrollInventoryTo(int slotNumber) {
    inventory.scrollTo(slotNumber);
    synchronizeInventory();
}

void Player::synchronizeInventory() {
    if ((currentItem->ID == inventory.getCurrentItemID()) && (inventory.getCurrentItemID() != 0)) return;

    delete currentItem;
    currentItem = inventory.getCurrentItem();
}

void Player::startSwinging() {
    swinging = true;
}

void Player::stopSwinging() {
    swinging = false;
	// TODO: does this work?
	if (target.active) env.wgrid()[(int)target.block.y][(int)target.block.x]->stopHitting();
}

bool Player::isSwinging() {
    return swinging;
}

void Player::moveWithCollisionDetection() {
    ofRectangle correction(-.25, 0.00, 0.5, 1.75f);

    ofRectangle curr(pos.x+correction.x, pos.y+correction.y, correction.width, correction.height);
    ofRectangle next(curr.x+vel.x, curr.y+vel.y, curr.width, curr.height);

    ofRectangle next_i(curr.x, curr.y, curr.width, curr.height);
    bool xHit = false, yHit = false;
    for (float m = 0.05f; m <= 1.0f; m += 0.1) {
        // Y-direction
        if (!yHit) {
            next_i.y = ofLerp(curr.y, next.y, m);
            if (vel.y > 0) {
                if (!env.blockByCoord(curr.x+curr.width-0.01, next_i.y+next_i.height).transparent ||
                    !env.blockByCoord(curr.x+0.01, next_i.y+next_i.height).transparent) { //upper
                    next.y = curr.y = (int)(next_i.y+next.height)-curr.height;
                    yHit = true;
                }
            } else {
                if (!env.blockByCoord(curr.x+curr.width-0.01, next_i.y).transparent ||
                    !env.blockByCoord(curr.x+0.01, next_i.y).transparent) { //lower
                    next.y = curr.y = (int)(next_i.y)+1;
                    yHit = true;
                }
            }
        }

        // X-direction
        if (!xHit) {
            next_i.x = ofLerp(curr.x, next.x, m);
            if (!env.blockByCoord(next_i.x+next.width, curr.y+0.01).transparent ||
                !env.blockByCoord(next_i.x+next.width, curr.y+1).transparent ||
                !env.blockByCoord(next_i.x+next.width, curr.y+curr.height-0.01).transparent) { //rhs
                next.x = curr.x = (int)(next_i.x+next.width)-curr.width;
                xHit = true;
            }
            if (!env.blockByCoord(next_i.x, curr.y+0.01).transparent ||
                !env.blockByCoord(next_i.x, curr.y+1).transparent ||
                !env.blockByCoord(next_i.x, curr.y+curr.height-0.01).transparent) { //lhs
                next.x = curr.x = (int)(next_i.x)+1;
                xHit = true;
            }
        }

        // Stop looping if we're done
        if (xHit && yHit) break;
    }

    //if (xHit) vel.x = 0.0f;
    if (yHit) vel.y = 0.0f;

    pos.set(next.x-correction.x, next.y-correction.y);
}

bool Player::hasMovementKeyDown() {
    return keyAPressed || keyDPressed;
}

void Player::resumeVelocity() {
    if (keyAPressed)
        vel.x = -xSpeed*speedModifier;
    else if (keyDPressed)
        vel.x = xSpeed*speedModifier;
}

float Player::xPos() {
    return pos.x;
}

float Player::yPos() {
    return pos.y;
}
